
#include <BOpenGL>
#include <BProgram>
#include <BMaterial>
#include <BTexture>
#include <BMatrix>

#include <BCamera>
#include <BViewer>
#include <BLight>

#include <BShape>
#include <BShape>
#include <BRender>
#include "member_BRender.h"
#include "member_BNode.h"
#include "member_BWidget.h"
#include "member_BShape.h"
#include "member_BShape.h"
#include "member_BProgram.h"
#include "member_BTexture.h"
#include "member_BMaterial.h"
#include "member_BCamera.h"

using namespace BWE;

#define member					(*(member_BRender*)_ptr)
#define member_allocate()		_ptr = new member_BRender(this)
#define member_release()		delete (member_BRender*)_ptr

BRender::BRender(BCamera* camera)
{
	member_allocate();
	member.save();
	member.camera = camera;
	IPixmap* pixmap = camera_member(camera)->pixmap;
	if (!camera || !pixmap->_fbo)
		return;

	const BSize& size = camera_member(camera)->pixmap->size();
	glViewport(0, 0, size.width(), size.height());

	BVector position = camera->position();
	BVector center = camera->center();
	BVector up = camera->up();
	up.normalize();

	BMatrix modelViewMatrix;
	modelViewMatrix.lookAt(position, center, up);

	double left = camera->left();
	double right = camera->right();
	double top = camera->top();
	double bottom = camera->bottom();
	double near = camera->near();
	double far = camera->far();

	glBindFramebuffer(GL_FRAMEBUFFER, pixmap->_fbo);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if (camera->ortho())
		glOrtho(left, right, bottom, top, near, far);
	else
		glFrustum(left, right, bottom, top, near, far);


	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glLoadMatrixf((GLfloat*)&modelViewMatrix);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

}
BRender::~BRender()
{
	member.open();
	member_release();
	glDisable(GL_DEPTH_TEST);
}

BCamera* BRender::camera() const
{
	return member.camera;
}

void BRender::setMatrix(const BMatrix& matrix)
{
	glMultMatrixf((const GLfloat*)&matrix);
}

void BRender::setColor(const BColor& color)
{
	glColor4ub(color.r(), color.g(), color.b(), color.a());
}
void BRender::setColor(BByte r, BByte g, BByte b, BByte a)
{
	glColor4ub(r, g, b, a);
}

void BRender::setLighting(bool lighting)
{
	if(lighting)
	{
		glEnable(GL_LIGHTING);
	}
	else
	{
		glDisable(GL_LIGHTING);
	}
	member.lighting = lighting;
}
bool BRender::lighting() const
{
	return member.lighting;
}

void BRender::setDepthTest(bool depthTest)
{
	if(depthTest)
	{
		glEnable(GL_DEPTH_TEST);
	}
	else
	{
		glDisable(GL_DEPTH_TEST);
	}
	member.depthTest = depthTest;
}
bool BRender::depthTest() const
{
	return member.depthTest;
}

void BRender::setCullBack(bool cullBack)
{
	glEnable(GL_NORMALIZE);
	member.cullback = cullBack;
	if (cullBack)
	{
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);
	}
	else
	{
		glDisable(GL_CULL_FACE);
	}
}
bool BRender::cullBack() const
{
	return member.cullback;
}

void BRender::setNormalize(bool normalize)
{
	if (normalize)
	{
		glEnable(GL_NORMALIZE);
	}
	else
	{
		glDisable(GL_NORMALIZE);
	}
	member.normalize = normalize;
}
bool BRender::normalize() const
{
	return member.normalize;
}

void BRender::setLineSmooth(bool lineSmooth)
{
	member.lineSmooth = lineSmooth;
	if(lineSmooth)
	{
		glEnable(GL_LINE_SMOOTH);
	}
	else
	{
		glDisable(GL_LINE_SMOOTH);
	}
}
bool BRender::lineSmooth() const
{
	return member.lineSmooth;
}

void BRender::setLineWidth(int lineWidth)
{
	if(lineWidth <= 0)
		lineWidth = 1;
	glLineWidth(lineWidth);
	member.lineWidth = lineWidth;
}
int BRender::lineWidth() const
{
	return member.lineWidth;
}

void BRender::setPointSmooth(bool pointSmooth)
{
	if(pointSmooth)
	{
		glEnable(GL_POINT_SMOOTH);
	}
	else
	{
		glDisable(GL_POINT_SMOOTH);
	}
	member.pointSmooth = pointSmooth;
}
bool BRender::pointSmooth() const
{
	return member.pointSmooth;
}

void BRender::setPointSize(int pointSize)
{
	if(pointSize <= 0)
		pointSize = 1;
	glPointSize(pointSize);
	member.pointSize = pointSize;
}
int BRender::pointSize() const
{
	return member.pointSize;
}

void BRender::setPolygonMode(PolygonMode polygonMode)
{
	switch(polygonMode)
	{
	case Polygon_Point:
		glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
		break;
	case Polygon_Line:
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		break;
	case Polygon_Fill:
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		break;
	}
	member.polygonMode = polygonMode;
}
PolygonMode BRender::polygonMode() const
{
	return (PolygonMode)member.polygonMode;
}

void BRender::setPolygonSmooth(bool polygonSmooth)
{
	member.polygonSmooth = polygonSmooth;
	if (polygonSmooth)
	{
		glEnable(GL_POLYGON_SMOOTH);
	}
	else
	{
		glDisable(GL_POLYGON_SMOOTH);
	}
}
bool BRender::polygonSmooth() const
{
	return member.polygonSmooth;
}

void BRender::setPolygonOffset(char polygonOffset)
{
	int offset = member.polygonOffset;
	if (member.polygonOffset == polygonOffset)
		return;
	if (polygonOffset == 0)
	{
		glDisable(GL_POLYGON_OFFSET_POINT);
		glDisable(GL_POLYGON_OFFSET_LINE);
		glDisable(GL_POLYGON_OFFSET_FILL);
		member.polygonOffset = polygonOffset;
		return;
	}
	if (member.polygonOffset == 0)
	{
		glEnable(GL_POLYGON_OFFSET_POINT);
		glEnable(GL_POLYGON_OFFSET_LINE);
		glEnable(GL_POLYGON_OFFSET_FILL);
	}
	glPolygonOffset(polygonOffset, polygonOffset);
	member.polygonOffset = polygonOffset;
}
char BRender::polygonOffset() const
{
	return member.polygonOffset;
}

void BRender::setState(const BState* state)
{
	if (state && state->able())
	{
		setLighting(state->lighting());
		setDepthTest(state->depthTest());
		setCullBack(state->cullBackface());
		setNormalize(state->normalize());
		setLineWidth(state->lineWidth());
		setLineSmooth(state->lineSmooth());
		setPointSize(state->pointSize());
		setPointSmooth(state->pointSmooth());
		setPolygonMode(state->polygonMode());
	}
}
void BRender::setProgram(const BProgram* program)
{
	if (program && program->able())
	{
		bool ret = program_member(program)->use();
	}
	else
	{
		glUseProgram(0);
	}
}
void BRender::setMaterial(const BMaterial* material)
{
	if (material && material->able())
	{
		const BColorf& color0 = material_member(material)->ambientColor;
		const BColorf& color1 = material_member(material)->diffuseColor;
		const BColorf& color2 = material_member(material)->specularColor;
		const BColorf& color3 = material_member(material)->emissionColor;
		glMaterialfv(GL_FRONT, GL_AMBIENT,		(const GLfloat*)&material_member(material)->ambientColor);
		glMaterialfv(GL_FRONT, GL_DIFFUSE,		(const GLfloat*)&material_member(material)->diffuseColor);
		glMaterialfv(GL_FRONT, GL_SPECULAR,		(const GLfloat*)&material_member(material)->specularColor);
		glMaterialfv(GL_FRONT, GL_EMISSION,		(const GLfloat*)&material_member(material)->emissionColor);
		glMaterialf (GL_FRONT, GL_SHININESS,	material->shininess());
	}
}
void BRender::setTexture(const BTexture* texture)
{
	if (member.texture == texture)
		return;
	if(texture && texture->able())
	{
		glEnable(GL_TEXTURE_2D);
		if (!texture->bind())
			return;
	}
	else
	{
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable(GL_TEXTURE_2D);
	}
	member.texture = texture;
}

void BRender::drawPoint(const BVector& point)
{
	glBegin(GL_POINTS);
		glVertex3f(point.x(), point.y(), point.z());
	glEnd();
}
void BRender::drawPoint(float x, float y, float z)
{
	glBegin(GL_POINTS);
		glVertex3f(x, y, z);
	glEnd();
}

void BRender::drawPoints(const BVector* points, int count)
{
	if (points && count > 0)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, points);
		glDrawArrays(GL_POINTS, 0, count);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::drawPoints(const BVectorArray& vertices)
{
	if (vertices.size())
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_POINTS, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

void BRender::drawLine(const BLine& line)
{
	glBegin(GL_LINES);
		glVertex3f(line.start().x(), line.start().y(), line.start().z());
		glVertex3f(line.end().x(), line.end().y(), line.end().z());
	glEnd();
}
void BRender::drawLine(const BVector& start, const BVector& end)
{
	glBegin(GL_LINES);
		glVertex3f(start.x(), start.y(), start.z());
		glVertex3f(end.x(), end.y(), end.z());
	glEnd();
}
void BRender::drawLine(float x0, float y0, float z0, float x1, float y1, float z1)
{
	glBegin(GL_LINES);
		glVertex3f(x0, y0, z0);
		glVertex3f(x1, y1, z1);
	glEnd();
}

void BRender::drawLines(const BLine* lines, int count)
{
	if (lines && count > 0)
	{
		for (int i = 0; i < count; i++)
		{
			drawLine(lines[i]);
		}
	}
}
void BRender::drawLines(const BVector* points, int size)
{
	if (points && size > 1)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, points);
		glDrawArrays(GL_LINES, 0, size);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::drawLines(const BVectorArray& vertices)
{
	if (vertices.size())
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_LINES, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

void BRender::drawLineLoop(const BVector* points, int size) const
{
	if(size < 3)
		return;
	glBegin(GL_LINE_LOOP);
	for(int i = 0; i < size; i++)
	{
		glVertex3f(points[i].x(), points[i].y(), points[i].z());
	}
	glEnd();
}
void BRender::drawLineLoop(const BVectorArray& points) const
{
	drawLineLoop(points.data(), points.size());
}

void BRender::drawLineStrip(const BVector* points, int size) const
{
	if(size < 3)
		return;
	glBegin(GL_LINE_STRIP);
	for(int i = 0; i < size; i++)
	{
		glVertex3f(points[i].x(), points[i].y(), points[i].z());
	}
	glEnd();
}
void BRender::drawLineStrip(const BVectorArray& points) const
{
	drawLineStrip(points.data(), points.size());
}

void BRender::drawTriangle(const BTriangle& triangle)
{
	const BVector& a = triangle.a();
	const BVector& b = triangle.b();
	const BVector& c = triangle.c();
	glBegin(GL_LINE_LOOP);
		glVertex3f(a.x(), a.y(), a.z());
		glVertex3f(b.x(), b.y(), b.z());
		glVertex3f(c.x(), c.y(), c.z());
	glEnd();
}
void BRender::drawTriangle(const BVector& a, const BVector& b, const BVector& c)
{
	glBegin(GL_LINE_LOOP);
		glVertex3f(a.x(), a.y(), a.z());
		glVertex3f(b.x(), b.y(), b.z());
		glVertex3f(c.x(), c.y(), c.z());
	glEnd();
}

void BRender::drawTriangles(const BTriangle* triangles, int count)
{
	if (triangles && count > 0)
	{
		for (int i = 0; i < count; i++)
		{
			drawTriangle(triangles[i]);
		}
	}
}
void BRender::drawTriangles(const BVector* points, int size)
{
	if (points && size > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, points);
		glDrawArrays(GL_POLYGON, 0, size);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::drawTriangles(const BVectorArray& vertices)
{
	if (vertices.size() > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_TRIANGLES, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

void BRender::fillTriangle(const BTriangle& triangle)
{
	const BVector& a = triangle.a();
	const BVector& b = triangle.b();
	const BVector& c = triangle.c();
	glBegin(GL_TRIANGLES);
	glVertex3f(a.x(), a.y(), a.z());
	glVertex3f(b.x(), b.y(), b.z());
	glVertex3f(c.x(), c.y(), c.z());
	glEnd();
}
void BRender::fillTriangle(const BVector& a, const BVector& b, const BVector& c)
{
	glBegin(GL_TRIANGLES);
	glVertex3f(a.x(), a.y(), a.z());
	glVertex3f(b.x(), b.y(), b.z());
	glVertex3f(c.x(), c.y(), c.z());
	glEnd();
}

void BRender::drawQuad(const BQuad& quad)
{
	drawQuad(quad.a(), quad.b(), quad.c(), quad.d());
}
void BRender::drawQuad(const BVector& a, const BVector& b, const BVector& c, const BVector& d)
{
	if (member.texture)
	{
		glBegin(GL_LINE_LOOP);
			glTexCoord2f(0, 0);		glVertex3f(a.x(), a.y(), a.z());
			glTexCoord2f(1, 0);		glVertex3f(b.x(), b.y(), b.z());
			glTexCoord2f(1, 1);		glVertex3f(c.x(), c.y(), c.z());
			glTexCoord2f(0, 1);		glVertex3f(d.x(), d.y(), d.z());
		glEnd();
	}
	else
	{
		glBegin(GL_LINE_LOOP);
			glVertex3f(a.x(), a.y(), a.z());
			glVertex3f(b.x(), b.y(), b.z());
			glVertex3f(c.x(), c.y(), c.z());
			glVertex3f(d.x(), d.y(), d.z());
		glEnd();
	}
}

void BRender::drawQuads(const BQuad* quads, int count)
{
	if (quads && count > 0)
	{
		for (int i = 0; i < count; i++)
		{
			drawQuad(quads[i]);
		}
	}
}
void BRender::drawQuads(const BVectorArray& vertices)
{
	if (vertices.size() > 3)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_QUADS, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

void BRender::fillQuad(const BQuad& quad)
{
	fillQuad(quad.a(), quad.b(), quad.c(), quad.d());
}
void BRender::fillQuad(const BVector& a, const BVector& b, const BVector& c, const BVector& d)
{
	if (member.texture)
	{
		glBegin(GL_QUADS);
			glTexCoord2f(0, 0);		glVertex3f(a.x(), a.y(), a.z());
			glTexCoord2f(0, 1);		glVertex3f(b.x(), b.y(), b.z());
			glTexCoord2f(1, 1);		glVertex3f(c.x(), c.y(), c.z());
			glTexCoord2f(1, 0);		glVertex3f(d.x(), d.y(), d.z());
		glEnd();
	}
	else
	{
		glBegin(GL_QUADS);
			glVertex3f(a.x(), a.y(), a.z());
			glVertex3f(b.x(), b.y(), b.z());
			glVertex3f(c.x(), c.y(), c.z());
			glVertex3f(d.x(), d.y(), d.z());
		glEnd();
	}
}

void BRender::fillQuads(const BVectorArray& vertices)
{
	if (vertices.size() > 3)
	{
		glDrawArrays(GL_QUADS, 0, vertices.size());
	}
}
void BRender::fillQuads(const BQuad* quads, int count)
{
	if (quads && count > 0)
	{
		for (int i = 0; i < count; i++)
		{
			fillQuad(quads[i]);
		}
	}
}

void BRender::drawPolygon(const BPolygon& polygon)
{
	drawPolygon(polygon.points());
}
void BRender::drawPolygon(const BVectorArray& vertices)
{
	if (0)//vertices.size() > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_LINE_LOOP, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	glBegin(GL_LINE_LOOP);
	for (int i = 0; i < vertices.size(); i++)
	{
		const BVector& v = vertices[i];
		glVertex3f(v.x(), v.y(), v.z());
	}
	glEnd();
}
void BRender::drawPolygon(const BVector* points, int size)
{
	if (points && size > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, points);
		glDrawArrays(GL_LINE_LOOP, 0, size);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::drawPolygon(const BVectorArray* vertices, const BIndexArray* indices)
{
	if (vertices && indices)
	{
		glBegin(GL_LINE_LOOP);
		for (int i = 0; i < indices->size(); i++)
		{
			const BVector& v = (*vertices)[i];
			glVertex3f(v.x(), v.y(), v.z());
		}
		glEnd();
	}
}

void BRender::fillPolygon(const BPolygon& polygon)
{
	fillPolygon(polygon.points());
}
void BRender::fillPolygon(const BVectorArray& vertices)
{
	if (vertices.size() > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_POLYGON, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::fillPolygon(const BVector* points, int size)
{
	if (points && size > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, points);
		glDrawArrays(GL_POLYGON, 0, size);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}
void BRender::fillPolygon(const BVectorArray* vertices, const BIndexArray* indices)
{
	if (vertices && indices)
	{
		glBegin(GL_POLYGON);
		for (int i = 0; i < indices->size(); i++)
		{
			const BVector& v = (*vertices)[i];
			glVertex3f(v.x(), v.y(), v.z());
		}
		glEnd();
	}
}

void BRender::drawTriangleFan(const BVector* points, int size)
{
	if (points && size > 2)
	{
		glBegin(GL_TRIANGLE_FAN);
		for (int i = 0; i < size; i++)
		{
			glVertex3f(points[i].x(), points[i].y(), points[i].z());
		}
		glEnd();
	}
}
void BRender::drawTriangleFan(const BVectorArray& vertices)
{
	if (vertices.size() > 2)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, vertices.data());
		glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
		glDisableClientState(GL_VERTEX_ARRAY);
	}
}

void BRender::drawSpace(const BSpace& space)
{
	BVector v0 = space.v0();
	BVector v1 = space.v1();
	BVector v2 = space.v2();
	BVector v3 = space.v3();
	BVector v4 = space.v4();
	BVector v5 = space.v5();
	BVector v6 = space.v6();
	BVector v7 = space.v7();

	drawQuad(v0, v1, v2, v3);
	drawQuad(v4, v5, v6, v7);
	drawLine(v0, v4);
	drawLine(v1, v5);
	drawLine(v2, v6);
	drawLine(v3, v7);
}
void BRender::fillSpace(const BSpace& space)
{
	BVector v0 = space.v0();
	BVector v1 = space.v1();
	BVector v2 = space.v2();
	BVector v3 = space.v3();
	BVector v4 = space.v4();
	BVector v5 = space.v5();
	BVector v6 = space.v6();
	BVector v7 = space.v7();

	fillQuad(v3, v2, v1, v0);
	fillQuad(v4, v5, v6, v7);
	fillQuad(v0, v1, v5, v4);
	fillQuad(v1, v2, v6, v5);
	fillQuad(v2, v3, v7, v6);
	fillQuad(v3, v0, v4, v7);
}
